use core::{mem::MaybeUninit, ops::Index};

pub struct CircleQueue<T, const SIZE: usize> {
    queue: [MaybeUninit<T>; SIZE],
    index: usize,
    full: bool,
}

impl<T, const SIZE: usize> CircleQueue<T, SIZE> {
    const INIT: MaybeUninit<T> = MaybeUninit::uninit();
    const MEM: [MaybeUninit<T>; SIZE] = [Self::INIT; SIZE];

    pub const fn new() -> Self {
        Self {
            queue: Self::MEM,
            index: 0,
            full: false,
        }
    }

    pub fn push(&mut self, value: T) {
        unsafe {
            let dst = self.queue.get_unchecked_mut(self.index);
            if self.full {
                dst.assume_init_drop();
            }
            *dst = MaybeUninit::new(value);
            self.index += 1;
            if self.index >= SIZE {
                self.full = true;
                self.index = 0;
            }
        }
    }

    pub fn len(&self) -> usize {
        if self.full {
            SIZE
        } else {
            self.index
        }
    }

    pub fn clear(&mut self) {
        unsafe {
            for i in 0..self.len() {
                self.queue.get_unchecked_mut(i).assume_init_drop();
            }
            self.index = 0;
            self.full = false;
        }
    }
}

impl<T, const SIZE: usize> Drop for CircleQueue<T, SIZE> {
    fn drop(&mut self) {
        for i in 0..self.len() {
            unsafe {
                self.queue.get_unchecked_mut(i).assume_init_drop();
            }
        }
    }
}

impl<T, const SIZE: usize> Index<usize> for CircleQueue<T, SIZE> {
    type Output = T;

    fn index(&self, index: usize) -> &Self::Output {
        unsafe { self.queue.get_unchecked(index).assume_init_ref() }
    }
}
